{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "sT8AyHRMNh41"
   },
   "source": [
    "# TensorFlow Recommenders: Quickstart"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "8f-reQ11gbLB"
   },
   "source": [
    "In this tutorial, we build a simple matrix factorization model using the [MovieLens 100K dataset](https://grouplens.org/datasets/movielens/100k/) with TFRS. We can use this model to recommend movies for a given user."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Import TensorFlow Recommender System (TFRS)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install -q sagemaker==2.9.2\n",
    "!pip install -q sagemaker-experiments==0.1.24\n",
    "!pip install -q tensorflow==2.3.0\n",
    "!pip install -q tensorflow-recommenders==0.2.0\n",
    "!pip install -q tensorflow-datasets==4.0.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "n3oYt3R6Nr9l"
   },
   "outputs": [],
   "source": [
    "from typing import Dict, Text\n",
    "\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "\n",
    "import tensorflow_datasets as tfds\n",
    "import tensorflow_recommenders as tfrs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "zCxQ1CZcO2wh"
   },
   "source": [
    "# Load Ratings"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "M-mxBYjdO5m7"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<PrefetchDataset shapes: {bucketized_user_age: (), movie_genres: (None,), movie_id: (), movie_title: (), raw_user_age: (), timestamp: (), user_gender: (), user_id: (), user_occupation_label: (), user_occupation_text: (), user_rating: (), user_zip_code: ()}, types: {bucketized_user_age: tf.float32, movie_genres: tf.int64, movie_id: tf.string, movie_title: tf.string, raw_user_age: tf.float32, timestamp: tf.int64, user_gender: tf.bool, user_id: tf.string, user_occupation_label: tf.int64, user_occupation_text: tf.string, user_rating: tf.float32, user_zip_code: tf.string}>\n"
     ]
    }
   ],
   "source": [
    "ratings = tfds.load('movielens/100k-ratings',                     \n",
    "                    download=False,\n",
    "                    data_dir='./tensorflow_datasets/',\n",
    "                    split='train')\n",
    "print(ratings)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<MapDataset shapes: {movie_title: (), user_id: ()}, types: {movie_title: tf.string, user_id: tf.string}>\n"
     ]
    }
   ],
   "source": [
    "ratings = ratings.map(lambda x: {\n",
    "    \"movie_title\": x[\"movie_title\"],\n",
    "    \"user_id\": x[\"user_id\"]\n",
    "})\n",
    "print(ratings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Load Movies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Movies BEFORE <PrefetchDataset shapes: {movie_genres: (None,), movie_id: (), movie_title: ()}, types: {movie_genres: tf.int64, movie_id: tf.string, movie_title: tf.string}>\n"
     ]
    }
   ],
   "source": [
    "movies = tfds.load('movielens/100k-movies', \n",
    "                   download=False,                   \n",
    "                   data_dir='./tensorflow_datasets/',\n",
    "                   split='train')\n",
    "print('Movies BEFORE', movies)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Movies AFTER <MapDataset shapes: (), types: tf.string>\n"
     ]
    }
   ],
   "source": [
    "movies = movies.map(lambda x: x[\"movie_title\"])\n",
    "print('Movies AFTER', movies)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "5W0HSfmSNCWm"
   },
   "source": [
    "# Create Vocabularies\n",
    "Build vocabularies to convert user ids and movie titles into integer indices for embedding layers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "9I1VTEjHzpfX"
   },
   "outputs": [],
   "source": [
    "user_ids_vocabulary = tf.keras.layers.experimental.preprocessing.StringLookup(mask_token=None)\n",
    "user_ids_vocabulary.adapt(ratings.map(lambda x: x[\"user_id\"]))\n",
    "\n",
    "movie_titles_vocabulary = tf.keras.layers.experimental.preprocessing.StringLookup(mask_token=None)\n",
    "movie_titles_vocabulary.adapt(movies)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['[UNK]', '405', '655', '13', '450', '276', '416', '537', '303', '234', '393', '181', '279', '429', '846', '7', '94', '682', '308', '92', '293', '222', '201', '59', '435', '378', '880', '417', '896', '592', '796', '758', '561', '130', '406', '551', '334', '804', '268', '474', '889', '269', '727', '399', '642', '916', '145', '650', '363', '151', '524', '749', '194', '387', '90', '648', '291', '864', '311', '747', '85', '286', '327', '653', '328', '385', '299', '497', '95', '271', '457', '18', '301', '532', '374', '805', '178', '1', '389', '870', '716', '883', '833', '472', '437', '313', '533', '881', '280', '339', '504', '184', '788', '894', '666', '314', '506', '932', '886', '798', '244', '343', '707', '606', '454', '109', '373', '354', '782', '62', '345', '790', '487', '207', '622', '892', '407', '588', '500', '774', '660', '312', '305', '711', '43', '535', '919', '854', '456', '618', '200', '102', '49', '495', '87', '6', '851', '868', '60', '256', '643', '452', '144', '843', '807', '425', '409', '479', '342', '64', '347', '840', '543', '379', '295', '246', '514', '346', '297', '659', '344', '486', '455', '577', '56', '326', '189', '897', '823', '267', '933', '128', '10', '815', '458', '42', '332', '125', '627', '198', '119', '11', '21', '174', '766', '5', '934', '318', '158', '398', '887', '621', '943', '82', '751', '721', '715', '757', '664', '586', '862', '608', '536', '712', '545', '380', '773', '249', '503', '262', '795', '254', '239', '663', '694', '593', '453', '83', '567', '548', '693', '58', '488', '320', '401', '44', '23', '848', '521', '294', '290', '699', '566', '498', '394', '738', '639', '601', '907', '654', '330', '296', '847', '221', '825', '436', '468', '442', '116', '936', '665', '325', '624', '224', '806', '16', '484', '447', '709', '270', '882', '72', '527', '99', '903', '899', '634', '542', '493', '445', '878', '541', '463', '230', '110', '913', '210', '70', '216', '336', '763', '496', '22', '214', '922', '698', '381', '298', '213', '321', '901', '671', '292', '236', '263', '645', '250', '661', '629', '391', '38', '361', '193', '927', '264', '160', '890', '632', '459', '197', '786', '871', '826', '690', '177', '705', '554', '478', '505', '307', '188', '392', '104', '921', '620', '233', '764', '756', '640', '489', '938', '748', '630', '617', '940', '863', '733', '26', '159', '141', '830', '625', '57', '152', '776', '708', '697', '223', '828', '741', '637', '15', '918', '615', '885', '835', '499', '397', '360', '560', '466', '426', '669', '553', '523', '911', '91', '587', '451', '14', '422', '253', '595', '195', '867', '275', '235', '838', '717', '63', '232', '215', '186', '115', '704', '605', '875', '619', '600', '323', '508', '315', '710', '684', '117', '724', '591', '552', '731', '476', '255', '924', '76', '877', '844', '568', '538', '243', '65', '585', '534', '465', '942', '75', '28', '25', '676', '251', '89', '559', '357', '217', '802', '683', '610', '449', '370', '338', '316', '288', '923', '908', '579', '121', '831', '752', '518', '501', '77', '562', '872', '860', '686', '658', '383', '274', '118', '771', '569', '331', '168', '167', '84', '526', '460', '402', '24', '746', '719', '638', '101', '865', '761', '73', '48', '372', '324', '778', '69', '54', '430', '329', '148', '829', '770', '768', '734', '423', '206', '106', '97', '930', '540', '180', '176', '164', '821', '679', '582', '432', '421', '287', '2', '931', '122', '910', '745', '480', '893', '8', '546', '483', '190', '100', '81', '647', '633', '507', '411', '367', '365', '348', '227', '161', '839', '787', '753', '490', '470', '395', '37', '257', '96', '539', '52', '481', '248', '187', '793', '79', '780', '492', '428', '135', '834', '525', '396', '3', '277', '123', '912', '735', '557', '528', '464', '382', '283', '218', '183', '555', '41', '388', '850', '573', '440', '412', '371', '322', '272', '157', '154', '138', '12', '113', '836', '413', '403', '350', '226', '939', '929', '902', '852', '677', '519', '590', '45', '237', '20', '185', '114', '904', '891', '869', '792', '703', '667', '603', '599', '580', '494', '284', '137', '837', '670', '668', '574', '265', '259', '112', '900', '623', '530', '368', '126', '714', '556', '467', '434', '424', '404', '351', '340', '884', '861', '616', '589', '529', '469', '358', '30', '203', '173', '781', '722', '644', '611', '352', '204', '179', '162', '906', '853', '760', '674', '597', '433', '349', '32', '937', '905', '692', '641', '550', '515', '199', '935', '859', '794', '784', '74', '718', '614', '196', '755', '730', '71', '695', '680', '66', '646', '448', '175', '779', '767', '607', '517', '502', '211', '169', '156', '817', '777', '689', '576', '446', '420', '31', '149', '917', '814', '743', '739', '673', '565', '477', '473', '40', '192', '136', '874', '754', '68', '675', '635', '564', '337', '29', '803', '789', '772', '750', '737', '701', '509', '491', '462', '438', '377', '366', '209', '208', '142', '108', '928', '925', '759', '691', '626', '419', '306', '285', '150', '845', '841', '702', '544', '471', '439', '427', '390', '898', '783', '720', '713', '67', '563', '531', '522', '510', '376', '229', '165', '131', '129', '879', '813', '80', '769', '706', '696', '672', '657', '602', '238', '146', '103', '827', '819', '800', '775', '678', '613', '609', '581', '53', '410', '375', '219', '182', '171', '17', '98', '842', '785', '744', '628', '612', '604', '583', '46', '408', '359', '289', '261', '225', '191', '172', '920', '915', '909', '856', '810', '801', '797', '742', '728', '575', '485', '482', '355', '333', '304', '281', '247', '133', '120', '832', '822', '816', '791', '725', '598', '594', '549', '47', '414', '362', '353', '35', '27', '212', '134', '799', '736', '726', '688', '656', '649', '584', '578', '511', '50', '444', '443', '415', '4', '356', '33', '260', '240', '139', '124', '111', '914', '86', '855', '849', '808', '765', '662', '547', '520', '51', '461', '386', '319', '278', '266', '258', '241', '163', '153', '127', '105', '941', '9', '857', '723', '681', '652', '570', '513', '400', '39', '384', '369', '335', '317', '282', '273', '245', '220', '205', '170', '155', '132', '107', '88', '876', '858', '820', '818', '811', '78', '762', '729', '700', '687', '651', '61', '55', '516', '512', '431', '341', '310', '302', '252', '231', '228', '140', '93', '926', '895', '888', '873', '866', '824', '812', '809', '740', '732', '685', '636', '631', '596', '572', '571', '558', '475', '441', '418', '364', '36', '34', '309', '300', '242', '202', '19', '166', '147', '143']\n"
     ]
    }
   ],
   "source": [
    "print(user_ids_vocabulary.get_vocabulary())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "Lrch6rVBOB9Q"
   },
   "source": [
    "# Create the Model\n",
    "\n",
    "We can define a TFRS model by inheriting from `tfrs.Model` and implementing the `compute_loss` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "e5dNbDZwOIHR"
   },
   "outputs": [],
   "source": [
    "class MovieLensModel(tfrs.Model):\n",
    "  # We derive from a custom base class to help reduce boilerplate. Under the hood,\n",
    "  # these are still plain Keras Models.\n",
    "\n",
    "  def __init__(\n",
    "      self,\n",
    "      user_embedding: tf.keras.Model,\n",
    "      movie_embeddings: tf.keras.Model,\n",
    "      task: tfrs.tasks.Retrieval):\n",
    "    super().__init__()\n",
    "\n",
    "    # Set up user and movie representations.\n",
    "    self.user_embeddings = user_embeddings\n",
    "    self.movie_embeddings = movie_embeddings\n",
    "\n",
    "    # Set up a retrieval task.\n",
    "    self.task = task\n",
    "\n",
    "  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:\n",
    "    # Define how the loss is computed using the retrieval task\n",
    "    user_embeddings = self.user_embeddings(features['user_id'])\n",
    "    movie_embeddings = self.movie_embeddings(features['movie_title'])\n",
    "\n",
    "    return self.task(user_embeddings, movie_embeddings)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "wdwtgUCEOI8y"
   },
   "source": [
    "# Define User and Movie Models\n",
    "Define the two models."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "EvtnUN6aUY4U"
   },
   "outputs": [],
   "source": [
    "user_embeddings = tf.keras.Sequential([\n",
    "    user_ids_vocabulary,\n",
    "    tf.keras.layers.Embedding(user_ids_vocabulary.vocab_size(), 128)\n",
    "])\n",
    "\n",
    "movie_embeddings = tf.keras.Sequential([\n",
    "    movie_titles_vocabulary,\n",
    "    tf.keras.layers.Embedding(movie_titles_vocabulary.vocab_size(), 128)\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Define the Retrieval Task"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "task = tfrs.tasks.Retrieval(metrics=tfrs.metrics.FactorizedTopK(\n",
    "    movies.batch(128).map(movie_embeddings)\n",
    "  )\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "BMV0HpzmJGWk"
   },
   "source": [
    "# Train the Retrieval Model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "colab": {},
    "colab_type": "code",
    "id": "H2tQDhqkOKf1",
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "model = MovieLensModel(user_embeddings, movie_embeddings, task)\n",
    "model.compile(optimizer=tf.keras.optimizers.Adagrad(0.5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25/25 [==============================] - 26s 1s/step - factorized_top_k/top_1_categorical_accuracy: 3.0000e-05 - factorized_top_k/top_5_categorical_accuracy: 6.0000e-04 - factorized_top_k/top_10_categorical_accuracy: 0.0028 - factorized_top_k/top_50_categorical_accuracy: 0.0312 - factorized_top_k/top_100_categorical_accuracy: 0.0762 - loss: 35713.1098 - regularization_loss: 0.0000e+00 - total_loss: 35713.1098\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<tensorflow.python.keras.callbacks.History at 0x7f57e063f250>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Train.\n",
    "model.fit(ratings.batch(4096), epochs=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Make Predictions\n",
    "Use brute-force search to set up retrieval using the trained representations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<tensorflow_recommenders.layers.factorized_top_k.BruteForce at 0x7f57e02e20d0>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "index = tfrs.layers.factorized_top_k.BruteForce(model.user_embeddings)\n",
    "index.index(movies.batch(100).map(model.movie_embeddings), movies)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "local_model_dir_bruteforce_model = './exported_models/bruteforce_model/'\n",
    "\n",
    "tensorflow_saved_model_path_bruteforce_model = os.path.join(local_model_dir_bruteforce_model, 'tensorflow/saved_model/0')\n",
    "\n",
    "os.makedirs(tensorflow_saved_model_path_bruteforce_model, exist_ok=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Top 5 recommendations for user 42: [b'Just Cause (1995)' b'Rent-a-Kid (1995)' b'Aristocats, The (1970)'\n",
      " b'Nell (1994)' b'Outbreak (1995)']\n"
     ]
    }
   ],
   "source": [
    "k = 5\n",
    "user_id = \"42\"\n",
    "\n",
    "_, titles = index(np.array([user_id]))\n",
    "\n",
    "print(f\"Top {k} recommendations for user {user_id}: {titles[0, :k]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Compiled model <tensorflow_recommenders.layers.factorized_top_k.BruteForce object at 0x7f57e02e20d0>\n",
      "Model: \"brute_force\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "sequential (Sequential)      (None, 128)               120832    \n",
      "=================================================================\n",
      "Total params: 337,810\n",
      "Trainable params: 120,832\n",
      "Non-trainable params: 216,978\n",
      "_________________________________________________________________\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "print('Compiled model {}'.format(index))          \n",
    "print(index.summary())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This property should not be used in TensorFlow 2.0, as updates are applied automatically.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: ./exported_models/bruteforce_model/tensorflow/saved_model/0/assets\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: ./exported_models/bruteforce_model/tensorflow/saved_model/0/assets\n"
     ]
    }
   ],
   "source": [
    "index.save(tensorflow_saved_model_path_bruteforce_model, save_format='tf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-11-03 06:25:32.286375: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:25:32.286412: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n",
      "\n",
      "MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:\n",
      "\n",
      "signature_def['__saved_model_init_op']:\n",
      "  The given SavedModel SignatureDef contains the following input(s):\n",
      "  The given SavedModel SignatureDef contains the following output(s):\n",
      "    outputs['__saved_model_init_op'] tensor_info:\n",
      "        dtype: DT_INVALID\n",
      "        shape: unknown_rank\n",
      "        name: NoOp\n",
      "  Method name is: \n",
      "\n",
      "signature_def['serving_default']:\n",
      "  The given SavedModel SignatureDef contains the following input(s):\n",
      "    inputs['input_1'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1)\n",
      "        name: serving_default_input_1:0\n",
      "  The given SavedModel SignatureDef contains the following output(s):\n",
      "    outputs['output_1'] tensor_info:\n",
      "        dtype: DT_FLOAT\n",
      "        shape: (-1, 10)\n",
      "        name: StatefulPartitionedCall:0\n",
      "    outputs['output_2'] tensor_info:\n",
      "        dtype: DT_STRING\n",
      "        shape: (-1, 10)\n",
      "        name: StatefulPartitionedCall:1\n",
      "  Method name is: tensorflow/serving/predict\n",
      "2020-11-03 06:25:34.989063: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:25:34.989114: W tensorflow/stream_executor/cuda/cuda_driver.cc:312] failed call to cuInit: UNKNOWN ERROR (303)\n",
      "2020-11-03 06:25:34.989153: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (datascience-1-0-ml-t3-medium-1abf3407f667f989be9d86559395): /proc/driver/nvidia/version does not exist\n",
      "Traceback (most recent call last):\n",
      "  File \"/opt/conda/bin/saved_model_cli\", line 8, in <module>\n",
      "    sys.exit(main())\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py\", line 1185, in main\n",
      "    args.func(args)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py\", line 715, in show\n",
      "    _show_all(args.dir)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py\", line 307, in _show_all\n",
      "    _show_defined_functions(saved_model_dir)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py\", line 187, in _show_defined_functions\n",
      "    trackable_object = load.load(saved_model_dir)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/saved_model/load.py\", line 603, in load\n",
      "    return load_internal(export_dir, tags, options)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/saved_model/load.py\", line 633, in load_internal\n",
      "    ckpt_options)\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/saved_model/load.py\", line 131, in __init__\n",
      "    self._restore_checkpoint()\n",
      "  File \"/opt/conda/lib/python3.7/site-packages/tensorflow/python/saved_model/load.py\", line 358, in _restore_checkpoint\n",
      "    \"%r from the checkpoint.\" % obj))\n",
      "NotImplementedError: Missing functionality to restore state of object <tensorflow.python.saved_model.load._RestoredResource object at 0x7f338963e590> from the checkpoint.\n"
     ]
    }
   ],
   "source": [
    "!saved_model_cli show --all --dir $tensorflow_saved_model_path_bruteforce_model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following works!\n",
    "\n",
    "\n",
    "```\n",
    "!saved_model_cli run --input_exprs 'input_1=[str(42)]' --tag_set serve --signature_def serving_default --dir $tensorflow_saved_model_path_bruteforce_model\n",
    "```\n",
    "\n",
    "```\n",
    "Result for output key output_1:\n",
    "[[3.9453535 2.9104383 2.7285933 2.6687438 2.5107827 2.4038887 2.3974428\n",
    "  2.386409  2.321732  2.3211298]]\n",
    "Result for output key output_2:\n",
    "[[b'Rent-a-Kid (1995)' b'Last Dance (1996)'\n",
    "  b'Adventures of Pinocchio, The (1996)'\n",
    "  b'Winnie the Pooh and the Blustery Day (1968)'\n",
    "  b'Aristocats, The (1970)' b'Celtic Pride (1996)'\n",
    "  b'Conan the Barbarian (1981)' b'House Arrest (1996)'\n",
    "  b'Just Cause (1995)' b'Johnny 100 Pesos (1993)']]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-11-03 06:25:36.132006: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:25:36.132051: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n",
      "2020-11-03 06:25:37.361429: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:25:37.361469: W tensorflow/stream_executor/cuda/cuda_driver.cc:312] failed call to cuInit: UNKNOWN ERROR (303)\n",
      "2020-11-03 06:25:37.361505: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (datascience-1-0-ml-t3-medium-1abf3407f667f989be9d86559395): /proc/driver/nvidia/version does not exist\n",
      "2020-11-03 06:25:37.361800: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN)to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA\n",
      "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "2020-11-03 06:25:37.372260: I tensorflow/core/platform/profile_utils/cpu_utils.cc:104] CPU Frequency: 2499995000 Hz\n",
      "2020-11-03 06:25:37.372448: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x55dde54b00d0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:\n",
      "2020-11-03 06:25:37.372480: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version\n",
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py:444: load (from tensorflow.python.saved_model.loader_impl) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This function will only be available through the v1 compatibility library as tf.compat.v1.saved_model.loader.load or tf.compat.v1.saved_model.load. There will be a new function for importing SavedModels in Tensorflow 2.0.\n",
      "INFO:tensorflow:Restoring parameters from ./exported_models/bruteforce_model/tensorflow/saved_model/0/variables/variables\n",
      "Result for output key output_1:\n",
      "[[0.450247   0.38484418 0.37186992 0.3443129  0.30999398 0.3028903\n",
      "  0.3025685  0.2935149  0.28972676 0.28972676]]\n",
      "Result for output key output_2:\n",
      "[[b'Paris, Texas (1984)' b'Madonna: Truth or Dare (1991)'\n",
      "  b'Race the Sun (1996)' b'Clockers (1995)' b'Ladybird Ladybird (1994)'\n",
      "  b'Duoluo tianshi (1995)' b'Higher Learning (1995)'\n",
      "  b'Good Man in Africa, A (1994)' b'Hugo Pool (1997)' b'Hugo Pool (1997)']]\n"
     ]
    }
   ],
   "source": [
    "!saved_model_cli run --input_exprs 'input_1=[\"$user_id\"]' --tag_set serve --signature_def serving_default --dir $tensorflow_saved_model_path_bruteforce_model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# All in one cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Args:\n",
      "Namespace()\n",
      "Environment Variables:\n",
      "{'AWS_ACCOUNT_ID': '835319576252',\n",
      " 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI': '/_sagemaker-instance-credentials/32ca01267e64c8dc379263a641297b882d1245c4f90e6ae159a4aaf6651942d1',\n",
      " 'AWS_DEFAULT_REGION': 'us-east-1',\n",
      " 'AWS_INTERNAL_IMAGE_OWNER': 'Studio',\n",
      " 'AWS_REGION': 'us-east-1',\n",
      " 'CLICOLOR': '1',\n",
      " 'GIT_PAGER': 'cat',\n",
      " 'HOME': '/root',\n",
      " 'HOSTNAME': 'datascience-1-0-ml-t3-medium-1abf3407f667f989be9d86559395',\n",
      " 'JPY_PARENT_PID': '8',\n",
      " 'JUPYTER_PATH': '/opt/conda/share/jupyter/',\n",
      " 'KERNEL_GATEWAY': '1',\n",
      " 'KERNEL_LAUNCH_TIMEOUT': '40',\n",
      " 'KERNEL_WORKING_PATH': 'workshop/02_usecases/sagemaker_recommendations',\n",
      " 'LANG': 'C.UTF-8',\n",
      " 'LC_ALL': 'C.UTF-8',\n",
      " 'MPLBACKEND': 'module://ipykernel.pylab.backend_inline',\n",
      " 'PAGER': 'cat',\n",
      " 'PATH': '/opt/conda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/amazon/bin:/opt/amazon/bin:/tmp/miniconda3/condabin:/tmp/anaconda3/condabin:/tmp/miniconda2/condabin:/tmp/anaconda2/condabin',\n",
      " 'PWD': '/root',\n",
      " 'REGION_NAME': 'us-east-1',\n",
      " 'SAGEMAKER_LOG_FILE': '/var/log/studio/kernel_gateway.log',\n",
      " 'SHELL': '/bin/bash',\n",
      " 'TERM': 'xterm-color',\n",
      " 'TF2_BEHAVIOR': '1'}\n",
      "output_dir ./output\n",
      "train_data ./tensorflow_datasets/\n",
      "epochs 1\n",
      "learning_rate 0.5\n",
      "enable_tensorboard False\n",
      "dataset_variant 100k\n",
      "embedding_dimension 256\n",
      "Ratings raw <PrefetchDataset shapes: {bucketized_user_age: (), movie_genres: (None,), movie_id: (), movie_title: (), raw_user_age: (), timestamp: (), user_gender: (), user_id: (), user_occupation_label: (), user_occupation_text: (), user_rating: (), user_zip_code: ()}, types: {bucketized_user_age: tf.float32, movie_genres: tf.int64, movie_id: tf.string, movie_title: tf.string, raw_user_age: tf.float32, timestamp: tf.int64, user_gender: tf.bool, user_id: tf.string, user_occupation_label: tf.int64, user_occupation_text: tf.string, user_rating: tf.float32, user_zip_code: tf.string}>\n",
      "Ratings transformed <MapDataset shapes: {movie_title: (), user_id: ()}, types: {movie_title: tf.string, user_id: tf.string}>\n",
      "Movies raw <PrefetchDataset shapes: {movie_genres: (None,), movie_id: (), movie_title: ()}, types: {movie_genres: tf.int64, movie_id: tf.string, movie_title: tf.string}>\n",
      "Movies transformed <MapDataset shapes: (), types: tf.string>\n",
      "Optimizer:  <tensorflow.python.keras.optimizer_v2.adagrad.Adagrad object at 0x7f57e008ba10>\n",
      "Callbacks: []\n",
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:The dtype of the source tensor must be floating (e.g. tf.float32) when calling GradientTape.gradient, got tf.int32\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Gradients do not exist for variables ['counter:0'] when minimizing the loss.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "25/25 [==============================] - 29s 1s/step - factorized_top_k/top_1_categorical_accuracy: 1.0000e-04 - factorized_top_k/top_5_categorical_accuracy: 9.1000e-04 - factorized_top_k/top_10_categorical_accuracy: 0.0028 - factorized_top_k/top_50_categorical_accuracy: 0.0239 - factorized_top_k/top_100_categorical_accuracy: 0.0579 - loss: 50904.1230 - regularization_loss: 0.0000e+00 - total_loss: 50904.1230\n",
      "Top 10 recommendations for user 42: [b'Aristocats, The (1970)' b'Deer Hunter, The (1978)'\n",
      " b'Adventures of Robin Hood, The (1938)' b\"Preacher's Wife, The (1996)\"\n",
      " b'Dolores Claiborne (1994)' b'Just Cause (1995)' b'M. Butterfly (1993)'\n",
      " b'Smoke (1995)' b'GoldenEye (1995)' b'Bronx Tale, A (1993)']\n",
      "Trained index <tensorflow_recommenders.layers.factorized_top_k.BruteForce object at 0x7f57e008b050>\n",
      "Model: \"brute_force_1\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "sequential_2 (Sequential)    (None, 256)               241664    \n",
      "=================================================================\n",
      "Total params: 673,938\n",
      "Trainable params: 241,664\n",
      "Non-trainable params: 432,274\n",
      "_________________________________________________________________\n",
      "None\n",
      "tensorflow_saved_model_path ./model/tensorflow/saved_model/0\n",
      "INFO:tensorflow:Assets written to: ./model/tensorflow/saved_model/0/assets\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Assets written to: ./model/tensorflow/saved_model/0/assets\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "import random\n",
    "import pandas as pd\n",
    "from glob import glob\n",
    "import pprint\n",
    "import argparse\n",
    "import json\n",
    "import subprocess\n",
    "import sys\n",
    "import os\n",
    "\n",
    "# Set the directory you want to start from\n",
    "#for dirName, subdirList, fileList in os.walk('/etc/ssl/certs/'):\n",
    "#    print('Found directory: %s' % dirName)\n",
    "#    for fname in fileList:\n",
    "#        print('\\t%s' % fname)        \n",
    "        \n",
    "from typing import Dict, Text\n",
    "import tensorflow as tf\n",
    "import tensorflow_datasets as tfds\n",
    "import tensorflow_recommenders as tfrs\n",
    "import numpy as np\n",
    "\n",
    "\n",
    "class MovieLensModel(tfrs.Model):\n",
    "  # We derive from a custom base class to help reduce boilerplate. Under the hood,\n",
    "  # these are still plain Keras Models.\n",
    "\n",
    "  def __init__(\n",
    "      self,\n",
    "      user_embedding: tf.keras.Model,\n",
    "      movie_embeddings: tf.keras.Model,\n",
    "      task: tfrs.tasks.Retrieval):\n",
    "    super().__init__()\n",
    "\n",
    "    # Set up user and movie representations.\n",
    "    self.user_embeddings = user_embeddings\n",
    "    self.movie_embeddings = movie_embeddings\n",
    "\n",
    "    # Set up a retrieval task.\n",
    "    self.task = task\n",
    "\n",
    "  def compute_loss(self, features: Dict[Text, tf.Tensor], training=False) -> tf.Tensor:\n",
    "    # Define how the loss is computed using the retrieval task\n",
    "    user_embeddings = self.user_embeddings(features['user_id'])\n",
    "    movie_embeddings = self.movie_embeddings(features['movie_title'])\n",
    "\n",
    "    return self.task(user_embeddings, movie_embeddings)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    parser = argparse.ArgumentParser()\n",
    "     \n",
    "    args, _ = parser.parse_known_args()\n",
    "    print(\"Args:\") \n",
    "    print(args)\n",
    "    \n",
    "    env_var = os.environ \n",
    "    print(\"Environment Variables:\") \n",
    "    pprint.pprint(dict(env_var), width = 1) \n",
    "\n",
    "    local_model_dir = './model' # os.environ['SM_MODEL_DIR']\n",
    "    output_dir = './output' # args.output_dir\n",
    "    print('output_dir {}'.format(output_dir))    \n",
    "\n",
    "    train_data = './tensorflow_datasets/' # args.train_data\n",
    "    print('train_data {}'.format(train_data))\n",
    "    epochs = 1 # args.epochs\n",
    "    print('epochs {}'.format(epochs))    \n",
    "    learning_rate = 0.5 # args.learning_rate\n",
    "    print('learning_rate {}'.format(learning_rate))    \n",
    "    enable_tensorboard = False # args.enable_tensorboard\n",
    "    print('enable_tensorboard {}'.format(enable_tensorboard))       \n",
    "    dataset_variant = '100k' # args.dataset_variant\n",
    "    print('dataset_variant {}'.format(dataset_variant))\n",
    "    embedding_dimension = int(256) # int(args.embedding_dimension)\n",
    "    print('embedding_dimension {}'.format(embedding_dimension))       \n",
    "\n",
    "    # Load the ratings data to use for training\n",
    "    ratings = tfds.load('movielens/{}-ratings'.format(dataset_variant), \n",
    "                        download=False,\n",
    "                        data_dir=train_data,\n",
    "                        split='train')\n",
    "    print('Ratings raw', ratings)\n",
    "\n",
    "    # Transform the ratings data specific to our training task\n",
    "    ratings = ratings.map(lambda x: {\n",
    "        'movie_title': x['movie_title'],\n",
    "        'user_id': x['user_id']\n",
    "    })\n",
    "    print('Ratings transformed', ratings)    \n",
    "\n",
    "    # Load the movies data to use for training\n",
    "    movies = tfds.load('movielens/{}-movies'.format(dataset_variant),\n",
    "                       download=False,\n",
    "                       data_dir=train_data,\n",
    "                       split='train')\n",
    "    print('Movies raw', movies)\n",
    "    \n",
    "    # Transform the movies data specific to our training task\n",
    "    movies = movies.map(lambda x: x['movie_title'])\n",
    "    print('Movies transformed', movies)\n",
    "\n",
    "    # Create the user vocabulary and user embeddings\n",
    "    user_ids_vocabulary = tf.keras.layers.experimental.preprocessing.StringLookup(mask_token=None)\n",
    "    user_ids_vocabulary.adapt(ratings.map(lambda x: x['user_id']))\n",
    "\n",
    "    user_embeddings = tf.keras.Sequential([\n",
    "        user_ids_vocabulary,\n",
    "        tf.keras.layers.Embedding(user_ids_vocabulary.vocab_size(),\n",
    "                                  embedding_dimension)\n",
    "    ])\n",
    "\n",
    "    # Create the movie vocabulary and movie embeddings\n",
    "    movie_titles_vocabulary = tf.keras.layers.experimental.preprocessing.StringLookup(mask_token=None)\n",
    "    movie_titles_vocabulary.adapt(movies)\n",
    "\n",
    "    movie_embeddings = tf.keras.Sequential([\n",
    "        movie_titles_vocabulary,\n",
    "        tf.keras.layers.Embedding(movie_titles_vocabulary.vocab_size(),\n",
    "                                  embedding_dimension)\n",
    "    ])\n",
    "\n",
    "    # Specify the task and the top-k metric to optimize during model training\n",
    "    task = tfrs.tasks.Retrieval(metrics=tfrs.metrics.FactorizedTopK(\n",
    "        movies.batch(128).map(movie_embeddings)\n",
    "    ))\n",
    "\n",
    "    # Define the optimizer and hyper-parameters\n",
    "    optimizer = tf.keras.optimizers.Adagrad(learning_rate)\n",
    "    print('Optimizer:  {}'.format(optimizer))\n",
    "\n",
    "    # Setup the callbacks to use during training\n",
    "    callbacks = []\n",
    "\n",
    "    # Setup the Tensorboard callback if Tensorboard is enabled\n",
    "    if enable_tensorboard: \n",
    "        # Tensorboard Logs \n",
    "        tensorboard_logs_path = os.path.join(local_model_dir, 'tensorboard/')\n",
    "        os.makedirs(tensorboard_logs_path, exist_ok=True)\n",
    "\n",
    "        tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=tensorboard_logs_path)\n",
    "        print('Adding Tensorboard callback {}'.format(tensorboard_callback))\n",
    "        callbacks.append(tensorboard_callback)\n",
    "    print('Callbacks: {}'.format(callbacks))\n",
    "\n",
    "    # Create a custom Keras model with the user embeddings, movie embeddings, and optimization task\n",
    "    model = MovieLensModel(user_embeddings, movie_embeddings, task)\n",
    "    \n",
    "    # Compile the model and prepare for training\n",
    "    model.compile(optimizer=optimizer)\n",
    "\n",
    "    # Train the model\n",
    "    model.fit(ratings.batch(4096), epochs=epochs)\n",
    "\n",
    "    # Make some sample predictions to test our model\n",
    "    # Note:  This is required to save and server our model with TensorFlow Serving\n",
    "    #        See https://github.com/tensorflow/tensorflow/issues/31057 for more  details.\n",
    "    index = tfrs.layers.factorized_top_k.BruteForce(query_model=model.user_embeddings)\n",
    "    index.index(movies.batch(100).map(model.movie_embeddings), movies)\n",
    "\n",
    "    user_id = '42'\n",
    "    _, titles = index(np.array([user_id]))\n",
    "\n",
    "    k = 10\n",
    "    print(f'Top {k} recommendations for user {user_id}: {titles[0, :k]}')\n",
    "\n",
    "    # Print a summary of our recommender model\n",
    "    print('Trained index {}'.format(index))\n",
    "    print(index.summary())\n",
    "\n",
    "    # Save the TensorFlow SavedModel for Serving Predictions\n",
    "    # SavedModel Output\n",
    "    tensorflow_saved_model_path = os.path.join(local_model_dir,\n",
    "                                               'tensorflow/saved_model/0')\n",
    "    os.makedirs(tensorflow_saved_model_path, exist_ok=True)\n",
    "    \n",
    "    print('tensorflow_saved_model_path {}'.format(tensorflow_saved_model_path))\n",
    "    index.save(tensorflow_saved_model_path, save_format='tf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-11-03 06:26:28.684049: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcudart.so.10.1'; dlerror: libcudart.so.10.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:26:28.684089: I tensorflow/stream_executor/cuda/cudart_stub.cc:29] Ignore above cudart dlerror if you do not have a GPU set up on your machine.\n",
      "2020-11-03 06:26:31.199874: W tensorflow/stream_executor/platform/default/dso_loader.cc:59] Could not load dynamic library 'libcuda.so.1'; dlerror: libcuda.so.1: cannot open shared object file: No such file or directory\n",
      "2020-11-03 06:26:31.199918: W tensorflow/stream_executor/cuda/cuda_driver.cc:312] failed call to cuInit: UNKNOWN ERROR (303)\n",
      "2020-11-03 06:26:31.199959: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:156] kernel driver does not appear to be running on this host (datascience-1-0-ml-t3-medium-1abf3407f667f989be9d86559395): /proc/driver/nvidia/version does not exist\n",
      "2020-11-03 06:26:31.201436: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN)to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA\n",
      "To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.\n",
      "2020-11-03 06:26:31.214262: I tensorflow/core/platform/profile_utils/cpu_utils.cc:104] CPU Frequency: 2499995000 Hz\n",
      "2020-11-03 06:26:31.214583: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x555a861beea0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:\n",
      "2020-11-03 06:26:31.214692: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version\n",
      "WARNING:tensorflow:From /opt/conda/lib/python3.7/site-packages/tensorflow/python/tools/saved_model_cli.py:444: load (from tensorflow.python.saved_model.loader_impl) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "This function will only be available through the v1 compatibility library as tf.compat.v1.saved_model.loader.load or tf.compat.v1.saved_model.load. There will be a new function for importing SavedModels in Tensorflow 2.0.\n",
      "INFO:tensorflow:Restoring parameters from ./model/tensorflow/saved_model/0/variables/variables\n",
      "Result for output key output_1:\n",
      "[[0.667718   0.5820437  0.5317137  0.52455425 0.50019515 0.49755943\n",
      "  0.48783603 0.4503791  0.432462   0.4121625 ]]\n",
      "Result for output key output_2:\n",
      "[[b'Madame Butterfly (1995)' b'Sabrina (1954)' b'Gigi (1958)'\n",
      "  b'When a Man Loves a Woman (1994)' b'Joy Luck Club, The (1993)'\n",
      "  b'Shiloh (1997)' b'Portrait of a Lady, The (1996)' b\"Gridlock'd (1997)\"\n",
      "  b'Great Race, The (1965)' b'Bad Company (1995)']]\n"
     ]
    }
   ],
   "source": [
    "user_id = \"42\"\n",
    "\n",
    "!saved_model_cli run --input_exprs 'input_1=np.array([\"$user_id\"])' --tag_set serve --signature_def serving_default --dir ./model/tensorflow/saved_model/0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "quickstart.ipynb",
   "private_outputs": true,
   "provenance": [],
   "toc_visible": true
  },
  "instance_type": "ml.t3.medium",
  "kernelspec": {
   "display_name": "Python 3 (Data Science)",
   "language": "python",
   "name": "python3__SAGEMAKER_INTERNAL__arn:aws:sagemaker:us-east-1:081325390199:image/datascience-1.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
